[contents] [prev] [next] [top] [bottom] (4 out of 6)

Defining and Using Modules

Modules can be defined only at the top level in a ScriptX program. To define a module, use the module definition expression. A module definition expression returns a ModuleClass object. The complete module definition expression is summarized here. Each of its optional clauses is covered in greater detail later in this chapter.

module ModuleName
[ exports variable, variable, variable, . . . ]
[ exports [ readonly ] instance variables variable, . . . ]
[ uses module, module, module, . . . ]
[ uses module with
[ imports everything ]
[ imports variable, variable, variable, . . . ]
[ imports [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ excludes variable, variable, variable, . . . ]
[ excludes [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ exports everything ]
[ exports variable, variable, variable, . . . ]
[ exports [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ prefix prefix ]
[ renames oldName:newName, oldName:newName, . . . ]
[ renames [ readOnly ] instance variables
voldName:newName, oldName:newName, . . . ]
end]
end
A module definition expression can include two main sections, which are both optional, and can be specified in any order. The exports options (described in the section "Exporting Variables to Other Modules" on page 202) specify which names are exported from this module. The uses options (described in the section "Importing Variables From Other Modules" on page 205) specify which names are imported from other modules.

In this syntax definition, and in many of the examples on the following pages, the individual sections of the module definition are shown on separate lines. You can specify a module definition in this way, all on a single line, or in any combination:

module foo exports x, y, z uses bar with prefix Z end end

module MyModule 
	exports varOne, varTwo
	uses foo with excludes Z end
end

Defining a Module

The first line of the module definition specifies the name this module is to be known by:

module ModuleName
...
end
Unlike ScriptX global variables, which are only global within the scope of a given module, module names are truly global. There is only one namespace for module names. Because of this, you should choose a module name that is unique and distinct:

module PaintInterfaceUser
module DeveloperModule_Rev15_345a
module LotsOfSillyClassesThatOnlyPartiallyWorkTogether 

By convention, module names are capitalized according to the same rules as class names. Initial letters are capitalized, with every succeeding word also capitalized.

Redefinition of Modules

Modules can be redefined. When you redefine a module, the new definition completely replaces the existing definition. By redefining a module, you can export additional variables, or define new use relationships.

Redefinition of a module does not break existing code at runtime. For example, if you redefine a module to exclude some variable from another module, which the module was already using, the module continues to use that variable. It is possible to import or export additional variables, but not to remove a variable from the list of variables that are imported or exported. The following example demonstrates this:

module Blue exports ink, paper end
module Pink uses Blue end
in module Blue
global ink := "blue"
global paper := "white"
in module Pink
global stone := ink + paper
"bluewhite"

-- now redefine Pink and change the value of ink from within Blue
module Pink uses Blue with excludes ink end end
in module Blue
ink := "red"
in module Pink
global Stone := ink + paper
"redwhite"

The global variable ink is still accessible in module Pink, even though it has been redefined to exclude ink, and ink continues to reflect the value that was set for it in module Blue.

The following example shows that it is possible to end up with more than one binding for a variable in a given module:

module Green exports grass, leaves end
module Brown uses Green end
in module Green
global grass := "green"
global leaves := "green"
in module Brown
global compost := grass + leaves
"greengreen"

-- now redefine Brown so that it renames grass, and change value
module Brown uses Green with renames grass:herbs end end
in module Green
grass := "brown"
in module Brown
compost := grass + leaves
"browngreen"
global moreCompost := herbs + leaves
"browngreen"

In the module Brown, the variable grass, as defined by Green, is accessible through both the original name and its new name. It has two valid bindings.

Developers should be aware that the redefinition of modules is a convenience at runtime. Redefinition of a module cannot resolve all possible cases. If a program needs to redefine a module to exclude or rename variables that are already being used in existing code, the best option is to recompile.

Switching Modules

All ScriptX expressions are compiled within the context of a given module. To move the scope of the ScriptX compiler from one module to another, use the following expression:

in module ModuleName
The in module expression places the ScriptX compiler (and therefore your Listener window or authoring environment) into the scope of the module specified by ModuleName, giving you access to all the variables defined in that module or imported from other modules. If the module you specify does not exist, ScriptX reports an exception. The ScriptX compiler remains in the specified module until in module is invoked again, placing the compiler in a different module. The in module expression is allowed only at the top level in your program.


Note - The ScriptX expression in module does not take a ModuleClass object as its target. It operates on a name literal, the scripter name of a module. Unlike other names in ScriptX, names of modules are not lexical names. With lexical names (names of constants and variables), the name represents the object itself. The name of a module is like a label for the module, a label which the in module expression recognizes. However, ScriptX functions that take a module as a parameter, such as the generic functions load and store, must be passed the ModuleClass object itself.

If you need access to the ScriptX core classes in your module, make sure the module that you specify in an in module expression has been defined to use the ScriptX module.

Generally, in module is used in a ScriptX program after its module definition, but before the remainder of the expressions (class definitions, variable declarations, and so on) that make up the program to be compiled within that module.

module DefinitionModule
exports FirstClass, sumThem
-- this exports getters and setters
exports instance variables a, b, c
uses ScriptX
end

module TestingModule
uses ScriptX, DefinitionModule
end

in module DefinitionModule
class FirstClass ()
	instance variables a, b, c
	instance methods 
		method init self #rest args #key a:(10) b:(10) c:(10) -> (
			apply nextMethod self args
			self.a := a
			self.b := b
			self.c := c
		)
		method sumThem self -> (
			print (self.a + self.b + self.c)
		)
end

in module TestingModule
global t := new FirstClass a:20
sumThem t
40

A new module can be defined within the scope of any other module, with the caveat that the module cannot use another module that is not yet defined. (For information on circular relationships, see page 207.) Defining a module does not switch the compiler into that module. Only the in module expression switches the compiler into another module..

Importing and Compiling Files with fileIn

The generic function fileIn, implemented by DirRep and ByteStream, is available only with the ScriptX Language and Class Library. (The Kaleida Media Player does not include the bytecode compiler, which compiles and executes scripts.) A large ScriptX project is typically compiled from a build file, and this build file usually includes a series of calls to fileIn. This design makes it easy to divide a large project into an number of smaller source files which are compiled in a set order.

Scripts that are imported using fileIn run in the Scratch module by default. The fileIn generic function defines a module keyword argument, which takes a ModuleClass object. This keyword can be used to specify which module the file is compiled in.

-- the file dogs.sx will be compiled in the AnimalInterface module
fileIn name:dogs.sx module:(getModule @AnimalInterface)

The in module expression, if it appears within a source file that is read in using fileIn, overrides fileIn in determining which module a given script is compiled in, but only within the scope of that file. Compilation reverts back to the previous module after fileIn finishes compiling and executing the script and returns a value.

The fileIn generic function It is possible to specify a module fileIn also specifies a module using the module keyword, the code within the file being imported is compiled within the named module. The fileIn generic function is described in the class definition of DirRep in the ScriptX Class Reference.

One disadvantage to using the module keyword with fileIn is that it can be hard to tell at a glance which module a given script is compiled in. For that reason, many programmers ignore the module keyword. They prefer to specify the current module explicitly at the top of each source file, using in module as the first expression in the file.

In contrast with fileIn, scripts that are read in and compiled using the Open Title . . . menu command in the current ScriptX Listener window run in the current environment, and place the ScriptX compiler directly into the modules specified by any in module expressions in the file.

Module Objects

The module definition, like all other ScriptX expressions, yields an object, in this case, an instance of ModuleClass. However, unlike class or function definition expressions, the name of the module is not a variable which is assigned to that module object, so you cannot use the module name to refer to the module object as you would any other variable.

In most cases you simply use the module as an environment in which you create and manipulate other objects. There may be cases, however, in which you need access to a ModuleClass object itself:

Once a module has been defined, you can get access to the module object itself using either the getModule or currentModule functions:

getModule ModuleName
The global function getModule returns the ModuleClass object that is specified by the given name. If you do not specify a valid module, getModule returns false. Unlike the in module expression, getModule cannot define a new module. The module referred to by ModuleName must already have been defined. Note that getModule does not affect which module ScriptX is currently compiling in. Only the in module expression can be used to switch from one module to another.

The getModule function returns false if it does not return a module. You can use this feature to test whether a module is defined or not:

if not (getModule @whatever) do 
(
	-- This block executes only if the module @whatever is not defined 
)

Once you have a module object to operate on, you can add that module to a container in the object store (as described in "Storing Modules" on page 222). For example, to append the Definition module to the tc title container:

append tc (getModule @Definition) 

You can use any other functions that operate directly on module objects (such as fileIn, described above). Of course, the ScriptX, Substrate, and Scratch modules, which are defined by the system, cannot be saved.

currentModule()
The global function currentModule, which is actually implemented as a macro, returns the ModuleClass object in which ScriptX is currently compiling. It reflects the state of the compiler during compilation, and is not meant to be used at runtime. ScriptX does not have a "currently active" module at runtime.

Do not use currentModule in scripts. It exists only for informational and debugging purposes.

deleteModule ModuleName
A module can be removed from memory using deleteModule, but only if it is not being used by any other module.

The global function deleteModule can be called with either the name of the module, or a ModuleClass object as its argument. If the module is being used by another module, and cannot be deleted, deleteModule reports the deletingUsedModule exception. If the modules that are using it are then deleted, the module can be deleted.

Exporting Variables to Other Modules

To specify which global variables are exported outside this module, use the exports section of the module definition expression:

module ModuleName
exports variable, variable, variable, . . .
end
The exports reserved word is followed by a list of variable names that are visible outside this module. Those variables can be variables that are defined, or will be defined, within this module. They can also be variables that were imported from other modules, and are thus "passed along" by this module. Variable names can be specified on separate lines, on the same line separated by commas, or in any combination.

module Australia
uses ScriptX
exports beer, engineers
end
in module Australia
global beer := "Foster's"
global engineers := #("Wainwright", "Nicholson", "Williams")

You can use multiple exports sections in your module definition, which is useful for documentation purposes or to group sets of exported variables together into logical groups. The resulting module exports all of the variables in all the exports sections.

module California
uses ScriptX
exports chips, software
exports almonds, avocados, cherries, figs, lettuce, wine
exports entertainment, movies
end
in module California
global chips := #("PowerPC", "Intel")
global software := "ScriptX"
. . .

A module must explicitly export all the names that are to be visible outside the module. The point of modules is really to exclude names, to export only those names that are required by other modules. Consider the following analogies between ScriptX and C.

Exporting Classes

You can export a class simply by specifying the variable that contains that class in an exports section of a module definition. However, be aware that exporting the class name alone does not automatically provide access to that class's methods and variables. To have full access to an exported class from another module, you must not only export the variable that contains that class, but you must also explicitly export.

If you do not explicitly export these variables, those parts of that class are unavailable to any modules that use this module. For example, suppose you have the following class definition, where the Person class has two instance variables, name and age, and two methods, printName and printAll.

class Person ()
	instance variables
		name, age
	instance methods
		method printName self -> (
			prin ("My name is " + self.name + "\n") @Normal debug
		)
		method printAll self -> (
			printName self
			prin ("My age is " + self.age + "\n") @Normal debug
		)
end

In order for both instance variables and both methods to be available outside the module, you must explicitly export all of the name bindings that are defined by the class:

module PersonModule
-- the class itself
exports Person
	-- Person's instance variables (getter and setter generics)
exports nameSetter, nameGetter, ageSetter, ageGetter
	-- Person's methods
exports printName, printAll
end

Exporting Instance Variables

To make exporting instance variables easier, the ScriptX module definition includes special syntax for exporting instance variables:

module ModuleName
exports [ readonly ] instance variables variable, variable, . . .
end
The instance variables reserved words can be shortened to instance vars or simply inst vars. The list of variables can be supplied on one line separated by commas, on separate lines, or in any combination.

The optional readonly reserved word exports the list of instance variables specified by variables in a read-only form; that is, they can be queried but not changed.

The instance variables part of an exports clause is simply shorthand for exporting the setter and getter generic functions for those variables. If the readonly option is specified, only getter methods are exported. The following module definitions are equivalent:

module MyModule
	exports readonly instance variables x
	exports instance variables y
end

module MyModule
	exports xGetter, ySetter, yGetter
end 

Importing Variables From Other Modules

To import variables from one module into another, use the uses clause of a module definition. There are two forms of uses: the short form (uses) that simply imports all the exported variables from the given module into this module, and the long form (uses . . . with), which allows control over which variables should be imported, and how they should be handled. For example, a uses . . . with clause can change their names or reexport them.

For the complete syntax for defining a module, see page 196. The syntax for both uses clauses in a module definition is as follows:

module ModuleName
[ uses module, module, module, . . . ]
[ uses module with
[ imports everything ]
[ imports variable, variable, variable, . . . ]
[ imports [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ excludes variable, variable, variable, . . . ]
[ excludes [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ exports everything ]
[ exports variable, variable, variable, . . . ]
[ exports [ readOnly ] instance variables
variable, variable, variable, . . . ]
[ prefix prefix ]
[ renames oldName:newName, oldName:newName, . . . ]
[ renames [ readOnly ] instance variables
voldName:newName, oldName:newName, . . . ]
end]
end
The line containing the reserved word uses specifies the modules whose variables are to be imported. The first form is simply the reserved word uses followed by the other modules whose variables are to be imported. Note that with this form, all variables are imported from the specified modules. The modules can be specified on the same line separated by commas, on separate lines, or in any combination. Any module you specify in a uses definition must already have been defined. You can have a single uses, or you can have multiple uses clauses in the same module definition. Note that if you want the variables in the ScriptX core classes to be available to the expressions in this module, you have to explicitly use the ScriptX module.

The second form of uses clause, uses with, allows more control over how imported variables from individual modules are handled. The uses with clause specifies options for a single module. You must use an individual uses with clause for each module.

The uses with clause contains several sub-clauses, referred to in this chapter as options, all of which are optional and may be included in any order. Also, although they have been presented here on multiple lines and indented, they may also be specified on a single line, or in any combination.

The uses form is equivalent to the uses with form with imports everything. That is, the following two definitions are equivalent:

module Mocha
	uses ScriptX
end


module Mocha
	uses ScriptX with
		imports everything
end

All uses with option are described in the following sections.

uses

module ModuleName
uses module, module, module, . . .
end
This is the simplest form of module definition that imports variables from other modules. All variables that are exported from the specified modules are imported into the module given by ModuleName.

A Note on Circular Use Relationships

A circular use relationship is defined as two modules that have uses clauses that import variables from each other. In this example, Foo uses Bar which uses Foo:

-- Don't define a circular relationship like this
module Foo 
	exports x, y, z 
	uses Bar 
end

module Bar 
	exports a, b, c 
	uses Foo 
end

Because a module's uses clause can only name modules that have already been defined, explicit circular use relationships cannot occur (the first definition returns a warning stating that module bar does not exist).

It is possible to define implicit circular use relationships between modules by exporting variable names from one module and defining those variables in another. This method of defining and using modules is described in "Organizing Modules" on page 214.

uses with imports

module ModuleName
uses module with
imports everything
imports variable, variable, variable, . . .
imports [ readOnly ] instance variables
variable, variable, variable, . . . ]
end
end
The optional imports clause is used to specify which variables to import from the module specified by module.

The imports option, when used with the reserved word everything, simply imports all the variables that the given module exports. If you use imports everything, you cannot use any of the other forms of imports in the same uses with clause. Also, if you omit all imports options in a uses with clause, imports everything is assumed. The advantage to using imports everything rather than a simple uses clause is that other options, such as prefix and renames, are also available.

The imports option followed by a list of variables specifies exactly which variables to import into this module. The variable names can be specified on separate lines, on the same line separated by commas, or in any combination.

The imports option with the instance variables reserved words (or the readOnly instance variables reserved words) is syntactic shorthand for importing the setter and getter generic functions for the named instance variables (or, in the case of readOnly, only the getter method). The instance variables reserved words can be shortened to instance vars or simply inst vars. See "Exporting Classes" on page 203 for more information on importing and exporting classes and their methods and variables.

The following example creates a module and exports three global variables it defines.

module XYZ
	uses ScriptX
	exports x, y, z
end
in module XYZ
global x:10, y:"foo", z:#(56,567)

The following module uses XYZ and imports all variables from it.

module XYZimport1
	uses ScriptX
	uses XYZ with imports everything end
end
in module XYZimport1
print x
10
print y
"foo"
print z
#(56, 567)

The third module uses XYZ, but it imports only x and z from this module. Since y is undefined within this module, attempting to access y from this module reports an exception.

module XYZimport2
	uses ScriptX
	uses XYZ with imports x, z end
end
in module XYZimport2
print x
10
print z
#(56,567)
print y -- this reports an exception
-- ** XYZimport2:y does not have a variable value \
(UninitializedVariable)

uses with renames

module ModuleName
uses module with
renames oldName:newName, oldName:newName, . . .
renames [ readOnly ] instance variables
oldName:newName, oldName:newName, . . .
end
end
The optional renames clause is used to import a variable from the given module and give it a new name. The definition of the variable, if any, is still valid in the new module under the new name. The renames keyword is followed by any number of oldName and newName pairs, on the same line separated by commas, on separate lines, or in any combination. The old and new variable names, oldName and newName, are separated by colons. The renames clause, used without an imports clause, assumes imports everything.

The renames clause with the instance variables reserved words (or the readOnly instance variables reserved words) is syntactic shorthand for renaming the setter and getter generic functions for the named instance variables (or, in the case of readOnly, only the getter method). See "Exporting Classes" on page 203 for more information on importing and exporting classes and their methods and variables.

The renames option overrides both import and prefix; the variables specified by renames are both imported and given the names specified by newName.

The following script sets up module XYZ, as in the previous example.

module XYZ
	uses ScriptX
	exports x, y, z
end
in module XYZ
global x:10, y:"foo", z:#(56,567)

This module uses module XYZ, imports only x, and renames it externalX.

module XYZrename1
	uses ScriptX
	uses XYZ with 
		renames x:externalX
	end
end
in module XYZrename1
print externalX
10
print y
"foo"

The next module explicitly imports x and y, but renames x. Renaming x overrides import x so that x is imported, but with a new name.

module XYZrename2
	uses ScriptX
	uses XYZ with
		imports x, y
		renames x:otherX
	end
end
in module XYZrename2
-- this reports an exception
print x 
-- ** XYZrename2:x does not have a variable value \
(UninitializedVariable)
print otherX
10
print y
"foo"

uses with prefix

module ModuleName
uses module with
prefix prefix
end
end
The optional prefix clause is used to import variables and rename them by attaching a prefix (specified by prefix) to the variable name. Prefixing variable names is useful for resolving conflicts in variable names between modules or for simply indicating which module a variable came from. The prefix option, used without an imports option, assumes imports everything.

You can assign a prefix to all imported variables and then rename specific variables by using the renames clause for those variables.

The following script sets up module XYZ, as in the previous example.

module XYZ
	uses ScriptX
	exports x, y, z
end
in module XYZ
global x:10, y:"foo", z:#(56,567)

Module XYZprefix1 imports all variables from module XYZ and prefixes variables from that module with XYZ_.

module XYZprefix1
	uses ScriptX
	uses XYZ with
		prefix XYZ_
	end
end
in module XYZprefix1
print x
-- ** XYZprefix1:x does not have a variable value
(UninitializedVariable)
print XYZ_x
10
print XYZ_y
"foo"

Module XYZprefix2 uses both a prefix clause and a renames clause. The renames clause overrides the prefix for specific variables.

module XYZprefix2
	uses ScriptX
	uses XYZ with
		prefix ext_
		renames x:fumbleWhizzy
	end
end
in module XYZprefix2
print ext_x
-- ** XYZprefix2:ext_x does not have a variable value
(UninitializedVariable)
print ext_y
"foo"
print fumbleWhizzy
10

uses with excludes

module ModuleName
uses module with
excludes variable, variable, variable, . . .
excludes [ readOnly ] instance variables
variable, variable, variable, . . .
end
end
The optional excludes clause is used to explicitly exclude individual variables from a module. The list of variable names can be on a single line separated by commas, on individual lines, or in any combination. If you use excludes without an imports clause, imports everything is assumed.

The excludes option with the instance variables reserved words (or the readOnly instance variables reserved words) is syntactic shorthand for excluding the setter and getter generic functions for the named instance variables (or, in the case of readOnly, only the getter method). See "Exporting Classes" on page 203 for more information on importing and exporting classes and their methods and variables.

Like renames and prefix, excludes is used to prevent imported variable names from clashing with variable names in the current module. However, excludes is also useful when there are a lot of imported variables and most, but not all, are relevant; rather than specifying all the variables this module is interested in with an import clause, you can simply specify the variables that this module is not concerned with using excludes.

The example uses module XYZ, just as in the previous section.

module XYZ
	uses ScriptX
	exports x, y, z
end
in module XYZ
global x:10, y:"foo", z:#(56,567)

This module imports all the variables from module XYZ, excluding y.

module XYZexclude1
	uses ScriptX
	uses XYZ with
		imports everything
		excludes y
	end
end
in module XYZexclude1
print x
10
print y
-- ** XYZexclude1:y does not have a variable value \
(UninitializedVariable)

uses with exports

module ModuleName
uses module with
exports everything
exports variable, variable, variable, . . .
exports [ readOnly ] instance variables
variable, variable, variable, . . .
end
end
The optional exports clause is used to re-export any variables that have been imported from module using the imports or renames clauses (sometimes called "transitive exporting"). You also use it to export variables created within this module. If the variables have been renamed or prefixed upon import (using the renames or prefix clauses), those variables are exported using those new names.

The exports option, when used with the reserved word everything, simply re-exports all the variables that were imported from module. If you use exports everything, you cannot use any of the other forms of exports in the same uses with clause.

The exports option followed by a list of variables specifies exactly which of the variables to re-export. The variable names can be specified on separate lines, on the same line separated by commas, or in any combination.

The exports option with the instance variables reserved words (or the readOnly instance variables reserved words) is syntactic shorthand for re-exporting the setter and getter generic functions for the named instance variables (or, in the case of readOnly, only the getter method). See "Exporting Classes" on page 203 for more information on importing and exporting classes and their methods and variables. The instance variables reserved words can be shortened to instance vars or simply inst vars. All are equivalent.

The example uses module XYZ, just as in the previous section.

module XYZ
	uses ScriptX
	exports x, y, z
end
in module XYZ
global x:10, y:"foo", z:#(56,567)

Module XYZexport, which exports its own variables (a and b), imports everything from module XYZ, renames x to otherX, and transitively exports otherX and y.

module XYZexport
exports a, b
uses ScriptX
uses XYZ with
imports everything
renames x:otherX
exports otherX, y
end
end

in module XYZexport
global a := "croissant"
global b := pi
print otherX
10

Module moreXYZexport uses XYZexport and imports all. The variables that get imported are y (from module XYZ), otherX (which is actually the variable x from module XYZ) and a and b (from module XYZexport).

module moreXYZexport
	uses ScriptX
uses XYZexport with
imports everything
end
end

in module moreXYZexport
print a
"croissant"
print b 
3.14159
print y 
"foo"
print otherX
10
print x
-- ** moreXYZexport:x does not have a variable value \
(UninitializedVariable)


This document is part of the ScriptX Language Guide, one of the volumes of the ScriptX Technical Reference Series. ScriptX is developed by the ScriptX Engineering Team at Apple Computer, successor to the Kaleida Engineering Team at Kaleida Labs, Inc.

Copyright 1996 Apple Computer, Inc. All Rights Reserved.